import * as vscode from 'vscode';
import {
  SnippetDefinition, SnippetDefinitionsResponse, TextSnippetDefinitionsRequest, SnippetDefinitionsRequest
} from '../entities/types';
import LRUCache from 'lru-cache';
import { Logger } from '../../../../src/logger/Logger';
import { DefinitionType } from '../entities/enum';


enum CxxLanguageServerCommand {
  documentSymbol = 'vscode.executeDocumentSymbolProvider',
  textSnippetDefinitions = 'wecode-cpp.providers.textSnippetDefinitions',
  snippetDefinitions = 'wecode-cpp.providers.snippetDefinitions',
  projInfo = 'wecode-cpp.project.getProjectInfo',
  prepareCallHierarchy = 'wecode-cpp.prepareCallHierarchy',
  provideIncomingCalls = 'wecode-cpp.provideIncomingCalls',
  documentReferences = 'vscode.executeReferenceProvider',
  documentPrepareTypeHierarchy = 'vscode.prepareTypeHierarchy',
  documentProvideSubtypes = 'vscode.provideSubtypes'
}

export class CxxLanguageServer implements vscode.Disposable {

  private documentSymbolCaches = new LRUCache<string, vscode.DocumentSymbol[]>({ max: 1000 });
  private documentReferencesCaches = new LRUCache<string, vscode.Location[]>({ max: 1000 });
  private documentTextCaches = new LRUCache<string,vscode.TextDocument>({ max: 1000 });
  private textSnippetDefinitionCaches = new LRUCache<string, SnippetDefinition[]>({ max: 1000 });
  private static _instance: CxxLanguageServer;

  private disposes: vscode.Disposable[] = [];

  private constructor() {
    this.disposes.push(
      vscode.workspace.onDidChangeTextDocument((e: any) => {
        this.updateDocumentSymbolCaches(e);
      })
    );
  }
  public static getInstance(): CxxLanguageServer {
    if (!this._instance) {
      this._instance = new CxxLanguageServer();
    }
    return this._instance;
  }

  public async getDocumentReferences(doc: vscode.TextDocument,range: vscode.Position): Promise<vscode.Location[]> {
    const documentReferences = this.documentReferencesCaches.get(doc.uri.path);
    if (documentReferences?.length) {
      return documentReferences;
    }
    const start = Date.now(); // 记录开始时间
    const results = await this.executeLanguageServerCommand<vscode.Location[]>(
      CxxLanguageServerCommand.documentReferences,
      doc.uri,
      range
    ) ?? [];
    const end = Date.now(); // 记录结束时间
    const executionTime = end - start; // 计算执行时间
    Logger.info(`getDocumentReferences time cost: ${executionTime}ms.`);
    this.documentReferencesCaches.set(doc.uri.path, results);
    return results;
  }

  public async getTextDocument(filePath:string): Promise<vscode.TextDocument> {
    const documentText = this.documentTextCaches.get(filePath);
    if (documentText) {
      return documentText;
    }
    const start = Date.now(); // 记录开始时间
    const results = await vscode.workspace.openTextDocument(filePath);
    const end = Date.now(); // 记录结束时间
    const executionTime = end - start; // 计算执行时间
    Logger.info(`getTextDocument time cost: ${executionTime}ms.`);
    this.documentTextCaches.set(filePath, results);
    return results;
  }

  public async getDocumentSymbol(doc: vscode.TextDocument): Promise<vscode.DocumentSymbol[]> {
    const documentSymbol = this.documentSymbolCaches.get(doc.fileName);
    if (documentSymbol?.length) {
      return documentSymbol;
    }
    const start = Date.now(); // 记录开始时间
    const results = await this.executeLanguageServerCommand<vscode.DocumentSymbol[]>(
      CxxLanguageServerCommand.documentSymbol,
      doc.uri
    ) ?? [];
    const end = Date.now(); // 记录结束时间
    const executionTime = end - start; // 计算执行时间
    Logger.info(`getDocumentSymbol time cost: ${executionTime}ms.`);
    this.documentSymbolCaches.set(doc.fileName, results);
    return results;
  }

  public async getTextSnippetDefinition(
    request: TextSnippetDefinitionsRequest,
    cancel?: vscode.CancellationToken
  ): Promise<SnippetDefinition[]> {
    const definitions: SnippetDefinition[] = [];
    // 优先从缓存中读取请求的符号定义
    request.symbols = request.symbols.filter(symbol => {
      const existDefinitions = this.textSnippetDefinitionCaches.get(symbol);
      if (existDefinitions && existDefinitions.length) {
        definitions.push(...existDefinitions);
      }
      return !existDefinitions;
    });

    if (!request.symbols.length) {
      return definitions;
    }
    const textSnippetDefinitions = await this.executeLanguageServerCommand<SnippetDefinitionsResponse>(
      CxxLanguageServerCommand.textSnippetDefinitions,
      request,
    );
    if (textSnippetDefinitions) {
      // 更新缓存
      textSnippetDefinitions.definitions.forEach((def) => {
        const existedDefintions = this.textSnippetDefinitionCaches.get(def.name) || [];
        this.textSnippetDefinitionCaches.set(def.name, [...existedDefintions, def]);
      });
      definitions.push(...textSnippetDefinitions.definitions);
    }
    return definitions;
  }

  public async getSnippetDefinition(
    request: SnippetDefinitionsRequest,
  ): Promise<SnippetDefinition[]> {
    const results = await this.executeLanguageServerCommand<SnippetDefinitionsResponse>(
      CxxLanguageServerCommand.snippetDefinitions,
      request,
    );
    return results?.definitions ?? [];
  }

  public async getCallHierarchyResult(
    document: vscode.TextDocument,
    active: vscode.Position,
    cancel?: vscode.CancellationToken
  ): Promise<vscode.CallHierarchyIncomingCall[]> {
    const item = await this.executeLanguageServerCommand<vscode.CallHierarchyItem>(
      CxxLanguageServerCommand.prepareCallHierarchy,
      document,
      active,
      cancel,
    );
    if (!item) {
      return [];
    }

    const results = await this.executeLanguageServerCommand<vscode.CallHierarchyIncomingCall[]>(
      CxxLanguageServerCommand.provideIncomingCalls,
      item,
      cancel,
    );

    return results ? results : [];
  }

  public async isIndexing() {
    const projInfo = await this.executeLanguageServerCommand<{
      isIndexing: boolean
    }>(CxxLanguageServerCommand.projInfo);
    return !!projInfo?.isIndexing;
  }

  public async isActivate() {
    const huaweiCpp = vscode.extensions.getExtension('spb-wecode.wecode-cpp');
    return huaweiCpp?.isActive;
  }

  private async updateDocumentSymbolCaches(e: vscode.TextDocumentChangeEvent) {
    const doc = e.document;
    if (doc.languageId !== 'cpp' && doc.languageId !== 'c') {
      return;
    }
    this.documentTextCaches.set(doc.uri.path, doc);
    const changes = e.contentChanges;
    if (!changes.length) {
      return;
    }
    // 变更出现换行，符号范围发生变化是才需要更新大纲以重新获取范围
    if (changes.length === 1 && !changes[0].text.match(/\n/g)) {
      return;
    }
    const documentSymbols = await this.executeLanguageServerCommand<vscode.DocumentSymbol[]>(
      CxxLanguageServerCommand.documentSymbol,
      doc.uri
    ) ?? [];
    this.documentSymbolCaches.set(doc.fileName, documentSymbols);
  }

  /**
  * 执行一个语言服务器命令并返回其结果。
  * @param command - 要执行的命令。
  * @param params - 命令的参数。
  * @returns 命令的响应结果，或在出错时返回 undefined。
  */
  private async executeLanguageServerCommand<T>(command: string, ...params: any[]): Promise<T | undefined> {
    try {
      const response = await vscode.commands.executeCommand<T>(command, ...params);
      return response;
    } catch (error) {
      Logger.error(`执行命令 ${command} 时出错: ${error}`);
      return undefined;
    }
  }

  dispose() {
    this.disposes.forEach(e => e.dispose());
  }
}
